﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace M3U8下载神器
{
    public class M3U8Package:ILock
    {
        public class TsInfo : ILock
        {
            public string Key { set; get; }
            public string Iv { set; get; }
            public string Method { set; get; } = "AES-128";

            public int Id { set; get; }
            /// <summary>
            /// Url 路径,以http(s)开头或/开头
            /// </summary>
            public string Path { set; get; }
            public bool Finish { set; get; } = false;

            public override string ToString()
            {
                return $"{{Id:{Id},Path:{Path},Finish:{Finish},Method:{Method},Key:{Key},Iv:{Iv}}}";
            }
        }
        public string Gid { set; get; }
        /// <summary>
        /// 最终文件保持位置
        /// </summary>
        public string SavePath { set; get; }
        /// <summary>
        /// 主机例如：http://127.0.0.1:8082/
        /// </summary>
        public string Host { set; get; }
        /// <summary>
        /// M3U8源
        /// </summary>
        public string Src { set; get; }
        /// <summary>
        /// 总计数
        /// </summary>
        public int Count { set; get; }

         public XQueue<TsInfo> WorkTsInfos = new XQueue<TsInfo>();
        // public HashSet<TsInfo> FinishTsInfos = new HashSet<TsInfo>();
        //  public List<TsInfo> ErrorTsInfos = new List<TsInfo>();
        public List<string> StreamInfos = new List<string>();

        /// <summary>
        /// 总记录，只用来初始化和导出导入
        /// </summary>
        public List<TsInfo> AllTsInfos = new List<TsInfo>();

        public bool Finish { set; get; } = false;

        public M3U8Package()
        {
           // AllTsInfos.Sort()
        }
        public int FinishCount => AllTsInfos.Sum((t) => t.Finish ? 1 : 0);
        public bool AllFinish()
        {
            return  FinishCount== Count;
        }

        public void Export(out string data, string path = null)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("#M3U8-1");
            sb.AppendLine("#Gid:" + Gid);
            sb.AppendLine("#Host:" + Host);
            sb.AppendLine("#Src:" + Src);
            sb.AppendLine("#SavePath:" + SavePath);
            sb.AppendLine("#Count:" + Count);
            foreach (var ts in AllTsInfos)
            {
                sb.AppendLine(ts.ToString());
            }
            sb.AppendLine("#End");
            data = sb.ToString();
            if (path != null)
            {
                using (var writer = File.CreateText(path))
                {
                    writer.Write(sb.ToString());
                }
            }
        }
        public static M3U8Package Import(string path, string savePath = null)
        {
            using (var reader = File.OpenText(path))
            {
                if (reader.ReadLine() != "#M3U8-1") return null;
                M3U8Package package = new M3U8Package();
                while (string.IsNullOrWhiteSpace(Ref.Out(reader.ReadLine(), out var line)))
                {
                    if (line.StartsWith("#"))
                    {
                        var ss = line.Split(':');
                        switch (ss[0])
                        {
                            case "#Gid":
                                package.Gid = ss[1]; break;
                            case "#Host":
                                package.Host = ss[1]; break;
                            case "#Src":
                                package.Src = ss[1]; break;
                            case "#SavePath":
                                package.SavePath = ss[1]; break;
                            case "#Count":
                                package.Count = ParseInt(ss[1], -1); break;
                        }
                    }
                    else
                    {
                        var ss = line.Split(',');
                        if (int.TryParse(ss[0], out var id) && bool.TryParse(ss[2], out var finish))
                        {
                            package.AllTsInfos.Add(new TsInfo { Id = id, Path = ss[1], Finish = finish });
                        }
                        else
                        {
                            return null;
                        }
                    }
                }
                if (savePath != null)
                {
                    package.SavePath = savePath;
                }
                if (package.Gid == null || package.Host == null || package.Src == null || package.SavePath == null || package.Count != package.AllTsInfos.Count) return null;
                return package;
            }
        }
        public static M3U8Package Parse(string src, string m3u8)
        {
            if (string.IsNullOrWhiteSpace(m3u8)) return null;
            var lines = m3u8.Split(new string[] { "\r\n", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries);
            if (lines == null || lines.Length < 3 || lines[0] != "#EXTM3U") return null;

            string prefix = GetPrefix(src, out var host);

            M3U8Package package = new M3U8Package() { Gid = Guid.NewGuid().ToString(), Host = host, Src = src };

            string key=null;
            string method=null;
            string iv=null;

            var id = 0;
            for (int i = 1; i < lines.Length; i++)
            {
                var line = lines[i];
                if (line.StartsWith("#"))
                {
                    if (line.StartsWith("#EXTINF:"))
                    {
                        var path = Ref.Out(lines[++i], out var l).StartsWith("#") ? lines[++i] : l;
                        package.AllTsInfos.Add(new TsInfo { Id = id++, Path = AbsPath(prefix, path), Finish = false,Key=key,Method=method,Iv=iv });
                    }
                    else if (line.StartsWith("#EXT-X-ENDLIST"))
                    {
                        break;
                    }
                    else if (line.StartsWith("#EXT-X-STREAM-INF"))
                    {
                        package.StreamInfos.Add(lines[++i]);
                    }else if (line.StartsWith("#EXT-X-KEY"))
                    {
                        method = null;
                        key = null;
                        iv = null;

                        var s = line.Split(new char[] { ':' },2)[1].Replace(" ","").Split(',');
                        foreach (var item in s)
                        {
                            if (item.ToUpper().StartsWith("METHOD="))
                            {
                                method = item.Substring(7);
                            }
                            if (item.ToUpper().StartsWith("IV="))
                            {
                                iv = item.Substring(3);
                            }
                            if (item.ToUpper().StartsWith("URI="))
                            {
                                var u = item.Substring(4).Replace("\"","");
                                string uri;
                                if (u.StartsWith("/"))
                                {
                                    uri = host + u;
                                }
                                else if (u.ToLower().StartsWith("http"))
                                {
                                    uri = u;
                                }
                                else
                                {
                                    uri =host+ prefix + u;
                                }

                                key = SimpleDownloader.Download2(uri,3);
                            }
                        }
                    }
                }
            }
            package.Count = package.AllTsInfos.Count;

            return package;
        }
        public bool Begin()
        {
            WorkTsInfos.Clear();
            foreach(var ts in AllTsInfos)
            {
                if (!ts.Finish)
                {
                    WorkTsInfos.Enqueue(ts);
                }
            }
            var di = new DirectoryInfo(Gid);
            di.Create();
            if (!new FileInfo(SavePath).Directory.Exists)
            {
                SaveFileDialog saveFileDialog = new SaveFileDialog();
                saveFileDialog.DefaultExt = ".ts";
                saveFileDialog.Filter = "Ts|*.ts";
                saveFileDialog.FileName = Path.GetFileName(SavePath);
                saveFileDialog.InitialDirectory = Path.GetDirectoryName(SavePath);
                if (saveFileDialog.ShowDialog() == DialogResult.OK)
                {
                    SavePath = saveFileDialog.FileName;
                    return true;
                }
                return false;
            }
            return true;
        }
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (var ts in AllTsInfos)
            {
                sb.Append(ts);
            }
            return $"{{Host:{Host}\r\nTsPath:\r\n{sb}}}";
        }
        public static int ParseInt(string v, int _default)
        {
            return int.TryParse(v, out var result) ? result : _default;
        }
        public static bool ParseBool(string v, bool _default)
        {
            return bool.TryParse(v, out var result) ? result : _default;
        }
        public static string GetPrefix(string m3u8Url, out string host)
        {
            int index1 = m3u8Url.IndexOf('/', 8);
            int index2 = m3u8Url.LastIndexOf('/');
            host = m3u8Url.Substring(0, index1);
            return m3u8Url.Substring(index1, index2 - index1 + 1);
        }
        /// <summary>
        /// 非http(s)或非/开头补成绝对路径
        /// </summary>
        /// <param name="prefix">以/开头并以/结尾</param>
        /// <param name="path">路径</param>
        /// <returns>返回http(s)开头或/开头的path</returns>
        public static string AbsPath(string prefix, string path)
        {
            if (path.StartsWith("/") || path.ToLower().StartsWith("http")) return path;
            return prefix + path;
        }
    }
}
